package model;

import operand.MyError;
import operand.MyStop;
import operand.MyValue;
import operand.Operand;
import operand.OperandVisitorWithoutResult;
import states.FirstOperandAvailable;
import states.NoOperandsAvailable;
import states.OperationStateVisitor;
import states.OperationStopped;
import states.SecondOperandAvailable;


public class SecondInputRunnable<T> extends InputRunnable<T> {
	public SecondInputRunnable(final Operation<T> operation,final  Variable<Operand<T>> output){
		super(operation,output);
	}
	@Override
	public void run() {
		while(this.getOperation().isThreadRunning()){
			final Operand<T> operand = this.getOperation().getSecondInputSource().get();
			doRun(operand);
		}
	}
	private void doRun(final Operand<T> secondOperand) {
		this.getOperation().getStateCriticalSection().lock();
		this.getOperation().getState().accept(new OperationStateVisitor<T>() {
			
			@Override
			public void visit(final NoOperandsAvailable<T> noOperandsAvailable) {
				secondOperand.accept(new OperandVisitorWithoutResult<T>() {

					
					@Override
					public void visit(final MyStop<T> stop) {
						SecondInputRunnable.this.getOperation().setThreadRunning(false);
						SecondInputRunnable.this.getOutput().put(stop);
						SecondInputRunnable.this.getOperation().setState(new OperationStopped<T>());
						SecondInputRunnable.this.getOperation().getStateCriticalSection().unlock();
					}

					@Override
					public void visit(final MyValue<T> value) {
						SecondInputRunnable.this.getOperation().setState(new SecondOperandAvailable<T>(secondOperand));
						SecondInputRunnable.this.getOperation().getStateCriticalSection().unlock();
					}

					@Override
					public void visit(final MyError<T> error) {
						SecondInputRunnable.this.getOperation().setState(new SecondOperandAvailable<T>(secondOperand));
						SecondInputRunnable.this.getOperation().getStateCriticalSection().unlock();
					}
				});
				
			}

			@Override
			public void visit(
					final FirstOperandAvailable<T> firstOperandAvailable) {
				SecondInputRunnable.this.getOutput().put(SecondInputRunnable.this.getOperation().calculate(firstOperandAvailable.getFirstOperand(), secondOperand));
				SecondInputRunnable.this.getOperation().setState(new NoOperandsAvailable<T>());
				SecondInputRunnable.this.getOperation().getStateCriticalSection().unlock();
			}

			@Override
			public void visit(
					final SecondOperandAvailable<T> secondOperandAvailable) {
				SecondInputRunnable.this.getOperation().getStateCriticalSection().unlock();
				SecondInputRunnable.this.getOperation().getSecondInputLock().lock();
				SecondInputRunnable.this.doRun(secondOperand);
			}

			@Override
			public void visit(final OperationStopped<T> operationStopped) {
				SecondInputRunnable.this.getOperation().getStateCriticalSection().unlock();
			}
		});
	}

}
